package org.hujunfeng.http;

import java.io.*;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.Date;

/**
 * @Author 胡俊丰
 * @Date 2024/7/23
 **/
public class Handler implements Runnable {

    /**
     * 客户端套接字
     */
    private Socket socket;

    /**
     * 项目的根路径
     */
    private String basePath = System.getProperty("user.dir") + "/../" + "webroot";

    /**
     * 默认页面
     */
    private String defaultPage = "/index.html";

    /**
     * 请求地址
     */
    private String requestURL = "";

    /**
     * 读取超时时间
     */
    private int readTimeout = 5000;

    /**
     * 请求类
     */
    private HttpRequest request;

    /**
     * 响应类
     */
    private HttpResponse response;

    public Handler(Socket socket){
        this(socket, 5000);
    }

    public Handler(Socket socket, int readTimeout) {
        this.socket = socket;
        this.readTimeout = readTimeout;

        basePath = replace(basePath);

        Logger.log("--------------------------------------------------------------------------------");
        Logger.log("----    current socket:" + socket.toString());
        Logger.log("----    current socket(hashCode):" + socket.hashCode());
        Logger.log("--------------------------------------------------------------------------------\n");
    }

    /**
     * 替换路径字符串
     * @param path
     * @return
     */
    private String replace(String path){
        StringBuffer temp = new StringBuffer(path);
        if (temp != null){
            for (int k = 0; k < path.length(); k++) {
                if (temp.charAt(k) == '\\'){
                    temp.setCharAt(k, '/');
                }
            }
        }
        return temp.toString();
    }

    @Override
    public void run() {
        try{
            socket.setSoTimeout(readTimeout);
            request = new HttpRequest(socket.getInputStream());
            response = new HttpResponse(socket.getOutputStream());
            String connection = null;

            // 长连接的处理逻辑
            while(true){
                // 读取请求
                request.parseRequest();
  
                // 处理请求，做出响应
                doResponse(request, response);

                connection = request.getHeader("connection");
 
                // 判断是否退出;是close模式；
                if(connection != null && connection.equals("close")){
                    break;
                }
            }

        } catch(IOException e){
            Logger.log("exception:" + e.getMessage());
        } finally {
            close(socket);
            Logger.log("socket close.");
        }
    }

    /**
     * 处理请求
     * @param path
     * @return
     */
    private void doResponse(HttpRequest request, HttpResponse response){
        try {
            String resourceUrl = request.getResource();

            if(resourceUrl == null || resourceUrl.equals("")){
                outBadRequest(response);
                Logger.log("bad request.");
                return;
            }

            requestURL = resourceUrl;
            File file = new File(basePath, resourceUrl);

            if(!request.getVersion().equals("HTTP/1.1") || !request.getMethod().toLowerCase().equals("get")){
                outNotAccept(response);
                return;
            }

            // 如果存在
            if(file.exists()){
                // 如果是文件，就输出文件
                if(file.isFile()){
                    outFile(response, file);
                } else {
                    // 如果是'/'结尾
                    if(resourceUrl.endsWith("/")){
                        // 获得默认文件
                        File defaultFile = new File(file, defaultPage);

                        // 如果默认文件存在
                        if(defaultFile.exists()){
                            outFile(response, defaultFile);
                            Logger.log("output file:" + resourceUrl);
                        } else {
                            outNotFound(response);
                            Logger.log(resourceUrl + " is not found.");
                        }
                    } else {
                        // 重定向请求
                        outLocation(response, resourceUrl + "/");
                    }
                } 
           } else{
               outNotFound(response);
               Logger.log(resourceUrl + " is not found.");
           }
        } catch (Exception e){
            try{
                outError(response);
            }catch(IOException e1){
                e1.printStackTrace();
            }
            Logger.log(e.getMessage());
            Logger.log("server is wrong.");
        }
    }

    /**
     * 根据文件路径获得类别
     *
     * @param path
     * @return
     */
    public String getContextType(String path) {
        // 获得后缀名
        String suffix = path.substring(path.lastIndexOf("."));

        Logger.log("The file suffix: [" + suffix + "]");

        String contentType = "";

        if (suffix.equalsIgnoreCase(".jpg")) {
            contentType = "image/jpeg";
        } else if (suffix.equalsIgnoreCase(".png")) {
            contentType = "image/png";
        } else if (suffix.equalsIgnoreCase(".ico")) {
            contentType = "image/x-icon";
        } else if (suffix.equalsIgnoreCase(".gif")) {
            contentType = "image/gif";
        } else if (suffix.equalsIgnoreCase(".css")) {
            contentType = "text/css";
        } else if (suffix.equalsIgnoreCase(".js")) {
            contentType = "text/javascript; charset=UTF-8";
        } else if (suffix.equalsIgnoreCase(".htm") || suffix.equalsIgnoreCase(".html")) {
            contentType = "text/html; charset=UTF-8";
        } else if (suffix.equalsIgnoreCase( ".pdf")){
            contentType = "application/pdf";
        } else if (suffix.equalsIgnoreCase(".xml")){
            contentType = "application/xml";
        } else if (suffix.equalsIgnoreCase(".json")){
            contentType = "application/json";
        } else if (suffix.equalsIgnoreCase(".ogg")){
            contentType = "audio/ogg";
        } else if (suffix.equalsIgnoreCase(".wav")){
            contentType = "audio/wav";
        } else if (suffix.equalsIgnoreCase(".mid")){
            contentType = "audio/midi";
        } else if (suffix.equalsIgnoreCase(".mpeg") || suffix.equalsIgnoreCase(".mpg")){
            contentType = "video/mpeg";
        } else if (suffix.equalsIgnoreCase(".mp4") || suffix.equalsIgnoreCase(".mp4a")){
            contentType = "video/mp4";
        } else if (suffix.equalsIgnoreCase(".mp3")){
            contentType = "audio/mpeg";
        } else if (suffix.equalsIgnoreCase(".txt")){
            contentType = "text/plain; charset=UTF-8";
        } else {
            contentType = "application/octet-stream";
        }

        return contentType;
    }

    /**
     * 初始化响应头
     *
     * @param response
     */
    private void initResponse(HttpResponse response){
        response.setVersion("HTTP/1.1");
        response.setHeader("Server", "Hu");
        response.setHeader("Content-Type", "text/html;charset=utf-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "-1");
        response.setHeader("Connection", "keep-alive");
        Date temp = new Date();
        response.setHeader("Date", temp.toString());
    }

    /**
     * 输出文件
     *
     * @param output
     * @param file
     */
    private void outFile(HttpResponse response, File file) {
        FileInputStream fileInput = null;
        try {
            fileInput = new FileInputStream(file);

            initResponse(response);
            response.setCode(200);
            response.setMessage("OK");

            response.setContentType(this.getContextType(file.getName()));
            response.setHeader("Content-Length", String.valueOf(file.length()));

            response.sendResponseHead();

            OutputStream output = response.getBodyOutput();

            byte[] buff = new byte[1024];

            int count = 0;

            // 循环读取文件，输出
            while ((count = fileInput.read(buff)) != -1) {
                output.write(buff, 0, count);
            }

            output.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 重定向请求
     * @param pw
     * @param resource
     */
    private void outLocation(HttpResponse response, String resource){
        initResponse(response);
        response.setCode(302);
        response.setMessage("Found");
        response.setHeader("Location", resource);
        
        response.sendResponseHead();
    }

    /**
     * 输出404错误
     *
     * @param pw
     */
    private void outNotFound(HttpResponse response) throws IOException {
        initResponse(response);
        response.setCode(404);
        response.setMessage("Sorry");
 
        String body = getMessageBody(404).replaceAll("\\$resource", requestURL);

        response.setHeader("Content-Length", String.valueOf(getLength(body, response)));

        response.sendResponseHead();

        OutputStream output = response.getBodyOutput();
        output.write(getBodyData(body, response));
        output.flush();
    }

    /**
     * 输出405不允许错误
     *
     * @param pw
     */
    private void outNotAccept(HttpResponse response) throws IOException{
        initResponse(response);
        response.setCode(405);
        response.setMessage("Not Accept");

        String body = getMessageBody(405).replaceAll("\\$errorname", "405 Request not Allowed")
                .replaceAll("\\$code", "405")
                .replaceAll("\\$description", "Request Not Allowed")
                .replaceAll("\\$explainning", "the request:" + requestURL + "is Not Allowed.")
                .replaceAll("$solution", "请联系网站维护工程师，或者尝试访问网站的其他资源。");

        response.setHeader("Content-Length", String.valueOf(getLength(body, response)));

        response.sendResponseHead();

        OutputStream output = response.getBodyOutput();
        output.write(getBodyData(body, response));
        output.flush();
    }

    /**
     * 输出400错误请求
     *
     * @param pw
     */
    private void outBadRequest(HttpResponse response) throws IOException {
        initResponse(response);
        response.setCode(400);
        response.setMessage("Bad Request");

        String body = getMessageBody(400).replaceAll("\\$errorname", "400 Bad Request")
                .replaceAll("\\$code", "400")
                .replaceAll("\\$description", "Request Is Bad.")
                .replaceAll("\\$explainning", "the request:" + requestURL + "is Bad.")
                .replaceAll("$solution", "请更正请求，再次尝试。");

        response.setHeader("Content-Length", String.valueOf(getLength(body, response)));

        response.sendResponseHead();

        OutputStream output = response.getBodyOutput();
        output.write(getBodyData(body, response));
        output.flush();
    }

    /**
     * 输出500错误
     *
     * @param pw
     */
    private void outError(HttpResponse response) throws IOException{
        initResponse(response);
        response.setCode(500);
        response.setMessage("error");

        String body = getMessageBody(500);

        response.setHeader("Content-Length", String.valueOf(getLength(body, response)));

        response.sendResponseHead();

        OutputStream output = response.getBodyOutput();
        output.write(getBodyData(body, response));
        output.flush();
    }

    /**
     * 获得/errors目录下的文件
     * @param code
     * @return
     */
    private String getMessageBody(int code){
        // 获得文件路径
        String filePath = basePath + "/../errors/" + code + ".html";

        File temp = new File(filePath);

        if (!temp.exists() && !temp.isFile()){
            filePath = basePath + "/../errors/errors.html";

            File temp1 = new File(filePath);
            if (!temp1.exists()){
                return "<!DOCTYPE html>\n" +
                        "<html>\n" +
                        "<head>\n" +
                        "    <title>$errorname</title>\n" +
                        "\n" +
                        "    <style type=\"text/css\">\n" +
                        "        #head{\n" +
                        "            width: 550px;\n" +
                        "            height: 45px;\n" +
                        "            line-height: 45px;\n" +
                        "            text-align: center;\n" +
                        "            font-size: 19px;\n" +
                        "            font-weight: bold;\n" +
                        "            background-color: rgb(217, 237, 247);\n" +
                        "            color: rgb(58, 135, 173);\n" +
                        "            margin-left: auto;\n" +
                        "            tmargin-right: auto;\n" +
                        "            margin-top: 40px;\n" +
                        "        }\n" +
                        "\n" +
                        "        #content{\n" +
                        "            width: 530px;\n" +
                        "            height: 450px;\n" +
                        "            font-size: 18px;\n" +
                        "            background-color: rgb(217, 237, 247);\n" +
                        "            color: rgb(45, 106, 150);\n" +
                        "            margin: 20px auto;\n" +
                        "            padding-top: 20px;\n" +
                        "            padding-left: 20px;\n" +
                        "        }\n" +
                        "\n" +
                        "    </style>\n" +
                        "\n" +
                        "</head>\n" +
                        "<body>\n" +
                        "    <div id=\"head\">\n" +
                        "        <p>$code $description</p>\n" +
                        "    </div>\n" +
                        "\n" +
                        "    <div id=\"content\">\n" +
                        "        <p>错误说明：$explainning</p>\n" +
                        "        <p>解决办法：$solution</p>             \n" +
                        "    </div>\n" +
                        "</body>\n" +
                        "</html>";
            }
        }

        StringBuffer buffer = new StringBuffer();

        try {
            InputStreamReader reader = new InputStreamReader(new FileInputStream(filePath), "UTF-8");
            char[] buff = new char[1024];
            int size = 0;
            while (true){
                size = reader.read(buff);
                if (size == -1){
                    break;
                }
                buffer.append(buff, 0, size);
            }
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }

    /**
     * 获得数据的长度
     * @param body
     * @return
     */
    private int getLength(String body, HttpResponse response){
        try {
            byte[] data = body.getBytes(response.getCharset());
            return data.length;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 获得数据的数组
     * @param body
     * @return
     */
    private byte[] getBodyData(String body, HttpResponse response){
        try {
            byte[] data = body.getBytes(response.getCharset());
            return data;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 关闭方法
     *
     * @param obj
     */
    private void close(Closeable... obj) {
        if (obj != null) {
            for (int k = 0; k < obj.length; k++) {
                try {
                    obj[k].close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
